package kms.robot;
 
import java.awt.geom.Point2D;

import robocode.AdvancedRobot;
import robocode.Robot;
import robocode.RobotDeathEvent;
import robocode.ScannedRobotEvent;
 
public class NghiaLuongRobot extends AdvancedRobot {

	private AdvancedEnemyBot enemy = new AdvancedEnemyBot();
	private byte radarDirection = 1;
	private byte moveDirection = 1;

	public void run() {
		
		//Can adjust radar when gun turn
		setAdjustRadarForGunTurn(true);
		//Can adjust gun when robot turn
		setAdjustGunForRobotTurn(true);

		while (true) {
			doRadar();
			doMove();
			doGun();
			execute();
		}
	}

	public void onScannedRobot(ScannedRobotEvent e) {

		// track if we have no enemy, the one we found is significantly
		// closer, or we scanned the one we've been tracking.
		if ( enemy.notAEnermy() || e.getDistance() < enemy.getDistance() ||
				e.getName().equals(enemy.getName())) {

			// track him using the NEW update method
			enemy.update(e, this);
		}
	}

	public void onRobotDeath(RobotDeathEvent e) {
		// see if the robot we were tracking died
		if (e.getName().equals(enemy.getName())) {
			enemy.reset();
		}
	}   

	void doRadar() {
		if (enemy.notAEnermy()) {
			// look around if we have no enemy
			setTurnRadarRightRadians(2*Math.PI);//360 degree
		} else {
			// oscillate the radar
			double turn = getHeading() - getRadarHeading() + enemy.getBearing();
			turn += 30 * radarDirection;
			setTurnRadarRight(normalizeBearing(turn));
			radarDirection *= -1;
		}
	}

	public void doMove() {
		// switch directions if we've stopped
		if (getVelocity() == 0){
			moveDirection *= -1;
		}
		// always square off against our enemy
		setTurnRight(normalizeBearing(enemy.getBearing() + 90));
		// circle our enemy
		setAhead(1000*moveDirection);
	}

	void doGun() {

		// don't shoot if I've got no enemy
		if (enemy.notAEnermy()){
			return;
		}
		
		// calculate firepower based on distance
		double firePower = Math.min(400 / enemy.getDistance(), 3);
		// calculate speed of bullet
		double bulletSpeed = 20 - firePower * 3;
		// distance = rate * time, solved for time
		long time = (long)(enemy.getDistance() / bulletSpeed);

		// calculate gun turn to predicted x,y location
		double futureX = enemy.getFutureX(time);
		double futureY = enemy.getFutureY(time);
		double absDeg = absoluteBearing(getX(), getY(), futureX, futureY);

		// turn the gun to the predicted x,y location
		setTurnGunRight(normalizeBearing(absDeg - getGunHeading()));

		// if the gun is cool and we're pointed in the right direction, shoot!
		if (getGunHeat() == 0 && Math.abs(getGunTurnRemaining()) < 10) {
			setFire(firePower);
		}
	}

	// computes the absolute bearing between two points
	double absoluteBearing(double x1, double y1, double x2, double y2) {
		double xo = x2-x1;
		double yo = y2-y1;
		double hyp = Point2D.distance(x1, y1, x2, y2);
		double arcSin = Math.toDegrees(Math.asin(xo / hyp));
		double bearing = 0;

		if (xo > 0 && yo > 0) { // both pos: lower-Left
			bearing = arcSin;
		} else if (xo < 0 && yo > 0) { // x neg, y pos: lower-right
			bearing = 360 + arcSin; // arcsin is negative here, actually 360 - ang
		} else if (xo > 0 && yo < 0) { // x pos, y neg: upper-left
			bearing = 180 - arcSin;
		} else if (xo < 0 && yo < 0) { // both neg: upper-right
			bearing = 180 - arcSin; // arcsin is negative here, actually 180 + ang
		}

		return bearing;
	}

	// normalizes a bearing to between +180 and -180
	double normalizeBearing(double angle) {
		while (angle >  180) angle -= 360;
		while (angle < -180) angle += 360;
		return angle;
	}
	
	class EnemyBot {
		private double bearing, distance, energy, heading, velocity;
		private String name;
		
		public EnemyBot() {
			reset();
		}
		
		public double getBearing() {
			return bearing;
		}
		public void setBearing(double bearing) {
			this.bearing = bearing;
		}
		public double getDistance() {
			return distance;
		}
		public void setDistance(double distance) {
			this.distance = distance;
		}
		public double getEnergy() {
			return energy;
		}
		public void setEnergy(double energy) {
			this.energy = energy;
		}
		public double getHeading() {
			return heading;
		}
		public void setHeading(double heading) {
			this.heading = heading;
		}
		public double getVelocity() {
			return velocity;
		}
		public void setVelocity(double velocity) {
			this.velocity = velocity;
		}
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		
		public void update(ScannedRobotEvent event) {
			setBearing(event.getBearing());
			setDistance(event.getDistance());
			setEnergy(event.getEnergy());
			setHeading(event.getHeading());
			setVelocity(event.getVelocity());
			setName(event.getName());
		}
		
		public void reset() {
			setName("");
			setBearing(0.0);
			setDistance(0.0);
			setEnergy(0.0);
			setHeading(0.0);
			setVelocity(0.0);
		}
		
		public boolean notAEnermy() {
			return name.equals("");
		}
	}
	class AdvancedEnemyBot extends EnemyBot {
		private double x, y;

		public AdvancedEnemyBot() {
			reset();
		}
		public double getX() {
			return x;
		}

		public double getY() {
			return y;
		}

		@Override
		public void reset() {
			super.reset();
			this.x = 0.0;
			this.y = 0.0;
		}
		
		public void update(ScannedRobotEvent e, Robot robot) {
			this.update(e);
			double absBearingDeg = (robot.getHeading() + e.getBearing());
			if (absBearingDeg < 0) absBearingDeg += 360;
			
			// yes, you use the _sine_ to get the X value because 0 deg is North
			x = robot.getX() + Math.sin(Math.toRadians(absBearingDeg)) * e.getDistance();
			
			// yes, you use the _cosine_ to get the Y value because 0 deg is North
			y = robot.getY() + Math.cos(Math.toRadians(absBearingDeg)) * e.getDistance();
		}
		
		public double getFutureX(long when) {
			return x + Math.sin(Math.toRadians(getHeading())) * getVelocity() * when;
		}
		
		public double getFutureY(long when) {
			return y + Math.cos(Math.toRadians(getHeading())) * getVelocity() * when;
		}
	}
}